﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using SQLLink.DataLayer;
using SQLLink.DomainClasses;

// ReSharper disable PossibleLossOfFraction

namespace DataStreamer
{
    public partial class Streamer : Form
    {
        private string _folderPath;                 //The path that will contain busLine files
        private Context Context = new Context();    //Used to write in the SQL database
        private Image bus;                          //The image of the bus for animation
        private int position;                       //The position of this image

        /// <summary>
        /// This is the Streamer Form constructor
        /// It pops the form, and does some initialization calls
        /// </summary>
        public Streamer()
        {
            //Pops the Streamer Form
            InitializeComponent();

            //Gives the bus animation an initial position
            var initialBus = new Bitmap(Directory.GetCurrentDirectory() + @"\..\..\bus.gif");
            float initialHeight = initialBus.Height;
            float initialWidth  = initialBus.Width;
            bus = initialBus.GetThumbnailImage((int)((90.0f / initialHeight) * initialWidth), 90, null, IntPtr.Zero);
            
            //Resets the database
            Context.Reset();

            //Writes to the database log
            textBoxWrittenToDatabase.Clear();
            Log("Base de données réinitialisée\r\n");
        }

        /// <summary>
        /// buttonSelectFolder_Click(object, EventArgs)
        /// This methods is called when the user clicks the select folder button
        /// It opens a folderBrowserDialog and remembers the selected folder into 
        /// the streamer's attribute variable _folderPath
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The event arguments</param>
        private void buttonSelectFolder_Click(object sender, EventArgs e)
        {
            _folderPath = "Sélectionnez un dossier !";
            textBoxParsingStatus.Text = "En attente";

            //Opens the folderBrowserDialog
            if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                _folderPath = folderBrowserDialog.SelectedPath;
            }

            buttonParseFile.Enabled = _folderPath != "Sélectionnez un dossier !";
            textBoxFolderPath.Text = _folderPath;
        }

        /// <summary>
        /// ParseFile(object, EventArgs)
        /// This method is called when the user clicks the parse file button.
        /// It parses the files contained in the folderPath previously selected
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The event arguments</param>
        private void ParseFile(object sender, EventArgs e)
        {
            //Gets the files from the folderPath
            var allFiles = Directory.GetFiles(_folderPath);
            
            //Clears this items from the comboBoxBusLine
            ComboBoxBusLine.Items.Clear();

            foreach (var file in allFiles)
            {
                int number; //Parses an int from the fileName. In case the parse fails, number = 0;
                int.TryParse(file.Substring(file.LastIndexOf("\\") + 1).Replace(".txt", ""), out number);

                //Creates a busLine
                var newBusLine = new BusLine
                                     {
                                         Number = number,
                                         GPRMCs = new List<GPRMC>(),
                                         Indexes = new List<int>(),
                                         Busses = new List<Bus>()
                                     };

                //Reads lines of the file
                var allLines = File.ReadAllLines(file);
                foreach (var str in allLines)
                {
                    if (str != "" && str.Contains("$GPRMC"))
                    {
                        //Adds a GPRMC to the busLine object
                        newBusLine.GPRMCs.Add(new GPRMC(str));
                    }
                }

                //Adds the busLine to the database
                Context.BusLines.Add(newBusLine);
                SaveContextChanges();

                //Writes to the database log
                Log("La ligne " + newBusLine + " a été ajoutée à la base de données\r\n");
                
                
                //Adds the busLine to the comboBoxBusLine and selects the first one
                ComboBoxBusLine.Items.Add(newBusLine);
                ComboBoxBusLine.SelectedIndex = 0;

                //Enables the comboBox if there are one or more busLine in the folder
                buttonAddBus.Enabled = ComboBoxBusLine.Items.Count > 0;
            }

            //Simulation is now ready
            buttonPlay.Enabled = true;

            //Writes to the database log
            textBoxParsingStatus.Text = allFiles.Count() + (allFiles.Count() > 0 ? " fichiers extraits":" fichier extrait");
        }

        /// <summary>
        /// buttonAddBus_Click(object, EventArgs)
        /// This method is called when the user clicks on the Add Bus button
        /// It adds a bus to the database according to the selected busline
        /// at the selected minute of its course.
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void buttonAddBus_Click(object sender, EventArgs e)
        {
            // Finds the selected busLine
            var number = ((BusLine) ComboBoxBusLine.SelectedItem).Number;
            var selectedBusLine = Context.BusLines.Where(m => m.Number == number).FirstOrDefault();

            // Starts the bus simulation at the selected GPRMC index
            var time = (int) numericUpDownStartTime.Value;

            // Adds the good index to the busLine's index list
            if (selectedBusLine != null)
            {
                //One index per second
                var index = time*60;

                //Adds the index to the busLine's list of indexes
                selectedBusLine.Indexes.Add(index);

                //Creates a bus
                var newBus = new Bus
                                 {
                                     ParentBusline = selectedBusLine,
                                     LineNumber = selectedBusLine.Number,
                                     Number = selectedBusLine.Busses.Count + 1,
                                     GPRMCs = new List<GPRMC>()
                                 };

                // Adds the bus to the database
                Context.Busses.Add(newBus);
                SaveContextChanges();

                //Writes to the database log
                Log("L'autobus #" + newBus.LineNumber + "-" + newBus.Number + 
                    " a été ajouté à la ligne " + selectedBusLine +
                    " à l'index " + index + "\r\n");
            }
        }

        private void SaveContextChanges()
        {
            try
            {
                Context.SaveChanges();
            }
            catch(Exception)
            {
                OutputTimer.Enabled = false;
                if(MessageBox.Show("Une erreur est survenue lors de l'écriture vers la base de données", 
                    "Erreur", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry)
                {
                    OutputTimer.Enabled = true;
                    SaveContextChanges();
                }
            }
        }

        /// <summary>
        /// OutputTimer_Tick(object, EventArgs)
        /// This method is called every second if the OutputTimer is set to enabled = true
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void OutputTimer_Tick(object sender, EventArgs e)
        {
            //Gets all the busLines from the database
            foreach (var busLine in Context.BusLines)
            {
                //If the user has added any bus on this busLine
                if (busLine.Indexes.Any())
                {
                    //For every contained indexes
                    for (var i = 0; i < busLine.Indexes.Count; i++)
                    {
                        //Gets the index
                        var index = busLine.Indexes[i];
                        
                        //Get the bus
                        var parentBus = busLine.Busses.ElementAt(i);

                        //If the index is still lower than the number of GPRMCs available for the busLine
                        if(index < busLine.GPRMCs.Count)
                        {
                            //Gets the busLine's GPRMC at the index
                            var GPRMC = busLine.GPRMCs[index];

                            //Sets its ParentBus
                            GPRMC.ParentBus = parentBus;
                           
                            //Adds it to the databse
                            Context.GPRMCs.Add(GPRMC);
                            
                            //Increments the index (for the next OutputTimer_Tick call)
                            busLine.Indexes[i]++;

                            //Writes to the database log
                            Log("Rafraîchissement automatique de l'autobus #" + busLine.Number + "-" + (i + 1) +
                                " à l'index " + index + "\r\n");
                        }  
                        //The index asked is bigger than the busLine's number of GPRMCs' indexes
                        else
                        {
                            //Removes the index from the indexes list
                            busLine.Indexes.RemoveAt(i);

                            //Writes to the database log
                            Log("L'autobus #" + busLine.Number + "-" + (i + 1) + " a terminé sa course\r\n");
                        }
                    }
                }
            }

            //Saves the database
            SaveContextChanges();
        }
        
        /// <summary>
        /// StartStreamer(object, EventArgs)   
        /// This methods is called when the users clicks on the start/stop simulation button
        /// It sets/resets the OutputTimer enabled field
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void StartStreamer(object sender, EventArgs e)
        {
            if (!OutputTimer.Enabled)
            {
                OutputTimer.Enabled = true;
                timerBusAnim.Enabled = true;
                buttonPlay.Text = "!!! ARRÊTER !!!";
            }
            else
            {
                OutputTimer.Enabled = false;
                timerBusAnim.Enabled = false;
                buttonPlay.Text = "!!! JOUER !!!";
            }
        }
        
        /// <summary>
        /// comboBoxBusRoute_SelectedIndexChanged(object, EventArgs) 
        /// This method happens when the index of the selected busLine changes
        /// It computes the maximum minute at which a user can start the bus on the selected busLine
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void comboBoxBusRoute_SelectedIndexChanged(object sender, EventArgs e)
        {
            numericUpDownStartTime.Maximum = ((BusLine)ComboBoxBusLine.SelectedItem).GPRMCs.Count / 60;
        }

        /// <summary>
        /// timerBusAnim_Tick(object sender, EventArgs e)
        /// This method is called to increment the position of the image of the bus animation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timerBusAnim_Tick(object sender, EventArgs e)
        {
            if (++position >= 1200)
            {
                position = 0;
            }

            pictureBoxBusStatus.Image = new Bitmap(1200, 90);
        }

        /// <summary>
        /// pictureBoxBusStatus_Paint(object, PaintEventArgs)
        /// This method redraws the image at its new position
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void pictureBoxBusStatus_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.DrawImage(bus, new Point(position, 0));
        }

        /// <summary>
        /// Log(string)
        /// This method allows to log data into the database log
        /// </summary>
        /// <param name="dataToLog">The data to log</param>
        private void Log(string dataToLog)
        {
            //Appends the data to log
            textBoxWrittenToDatabase.Text = textBoxWrittenToDatabase.Text.Insert(0, DateTime.Now.ToShortTimeString() + " - " + dataToLog);
        }
    }
}